/*
* Copyright (C) 2013 Johan Nilsson <http://markupartist.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.markupartist.sthlmtraveling;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.MenuItem;
import android.view.View;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.UiSettings;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.markupartist.sthlmtraveling.data.api.ApiService;
import com.markupartist.sthlmtraveling.data.models.Entrance;
import com.markupartist.sthlmtraveling.data.models.IntermediateStop;
import com.markupartist.sthlmtraveling.data.models.IntermediateResponse;
import com.markupartist.sthlmtraveling.data.models.Leg;
import com.markupartist.sthlmtraveling.data.models.Place;
import com.markupartist.sthlmtraveling.data.models.Route;
import com.markupartist.sthlmtraveling.data.models.Step;
import com.markupartist.sthlmtraveling.data.models.TravelMode;
import com.markupartist.sthlmtraveling.provider.planner.JourneyQuery;
import com.markupartist.sthlmtraveling.provider.site.Site;
import com.markupartist.sthlmtraveling.utils.Analytics;
import com.markupartist.sthlmtraveling.utils.LegUtil;
import com.markupartist.sthlmtraveling.utils.PolyUtil;
import com.markupartist.sthlmtraveling.utils.StringUtils;
import com.markupartist.sthlmtraveling.utils.ViewHelper;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;
public class ViewOnMapActivity extends BaseFragmentActivity implements OnMapReadyCallback {
private static final String TAG = "ViewOnMapActivity";
public static String EXTRA_LOCATION = "com.markupartist.sthlmtraveling.extra.Location";
public static String EXTRA_JOURNEY_QUERY = "com.markupartist.sthlmtraveling.extra.JourneyQuery";
public static String EXTRA_TRIP = "com.markupartist.sthlmtraveling.extra.Trip";
public static String EXTRA_ROUTE = "com.markupartist.sthlmtraveling.extra.Route";
/**
* Note that this may be null if the Google Play services APK is not available.
*/
private GoogleMap mMap;
private LatLng mFocusedLatLng;
private Route mTransitRoute;
private JourneyQuery mJourneyQuery;
private Route mRoute;
private ApiService mApiService;
public static Intent createIntent(Context context, JourneyQuery query, Route route) {
Intent intent = new Intent(context, ViewOnMapActivity.class);
intent.putExtra(ViewOnMapActivity.EXTRA_ROUTE, route);
intent.putExtra(ViewOnMapActivity.EXTRA_JOURNEY_QUERY, query);
return intent;
}
public static Intent createIntent(Context context, Route route, JourneyQuery query, Site location) {
Intent intent = new Intent(context, ViewOnMapActivity.class);
intent.putExtra(ViewOnMapActivity.EXTRA_TRIP, route);
intent.putExtra(ViewOnMapActivity.EXTRA_JOURNEY_QUERY, query);
intent.putExtra(ViewOnMapActivity.EXTRA_LOCATION, location);
return intent;
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
public void startActivity(Intent intent) {
super.startActivity(intent);
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
}
@Override
public void onBackPressed() {
super.onBackPressed();
overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Analytics.getInstance(this).registerScreen("View on map");
setContentView(R.layout.map);
ActionBar actionBar = getSupportActionBar();
//actionBar.setBackgroundDrawable(getResources().getDrawableColorInt(R.drawable.ab_bg_black));
actionBar.setHomeButtonEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.route_details_label);
Bundle extras = getIntent().getExtras();
mRoute = extras.getParcelable(EXTRA_ROUTE);
mTransitRoute = extras.getParcelable(EXTRA_TRIP);
mJourneyQuery = extras.getParcelable(EXTRA_JOURNEY_QUERY);
final Site focusedLocation = extras.getParcelable(EXTRA_LOCATION);
mApiService = MyApplication.get(this).getApiService();
if (focusedLocation != null) {
mFocusedLatLng = new LatLng(
focusedLocation.getLocation().getLatitude(),
focusedLocation.getLocation().getLongitude());
}
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
updateStartAndEndPointViews(mJourneyQuery);
}
public void loadIntermediateStops(final Route route) {
List<String> references = new ArrayList<>();
for (Leg leg : route.getLegs()) {
if (leg.getIntermediateStops().isEmpty() && leg.getDetailRef() != null) {
references.add(leg.getDetailRef());
}
}
if (references.isEmpty()) {
// If the legs did not have any references there's no need to ask for data.
return;
}
mApiService.getIntermediateStops(references, new Callback<IntermediateResponse>() {
@Override
public void success(IntermediateResponse intermediateResponse, Response response) {
for (Leg leg : route.getLegs()) {
if (leg.isTransit()) {
leg.setIntermediateStops(intermediateResponse.getStops(leg.getDetailRef()));
}
}
showTransitRoute(route);
}
@Override
public void failure(RetrofitError error) {
showTransitRoute(route);
}
});
}
public void showTransitRoute(Route route) {
mMap.clear();
for (Leg leg : route.getLegs()) {
int legColor = LegUtil.getColor(this, leg);
float[] hsv = new float[3];
Color.colorToHSV(legColor, hsv);
float hueColor = hsv[0];
LatLng origin = new LatLng(
leg.getFrom().getLat(),
leg.getFrom().getLon());
mMap.addMarker(new MarkerOptions()
.position(origin)
.title(getLocationName(leg.getFrom()))
.snippet(getRouteDescription(leg))
.icon(BitmapDescriptorFactory.defaultMarker(hueColor)));
LatLng destination = new LatLng(
leg.getTo().getLat(),
leg.getTo().getLon());
mMap.addMarker(new MarkerOptions()
.position(destination)
.title(getLocationName(leg.getTo()))
.snippet(DateFormat.getTimeFormat(this).format(leg.getStartTime()))
.icon(BitmapDescriptorFactory.defaultMarker(hueColor)));
if (leg.getGeometry() != null) {
// If we have a geometry draw that.
List<LatLng> latLgns = PolyUtil.decode(leg.getGeometry());
drawPolyline(latLgns, legColor);
} else {
// One polyline per leg, different colors.
PolylineOptions options = new PolylineOptions();
options.add(origin);
BitmapDescriptor icon = getColoredMarker(legColor, R.drawable.ic_line_marker);
for (IntermediateStop stop : leg.getIntermediateStops()) {
LatLng intermediateStop = new LatLng(
stop.getLocation().getLat(),
stop.getLocation().getLon());
options.add(intermediateStop);
Date date = stop.getTimeRt();
if (date == null) {
date = stop.getTime();
}
String time = date != null ? DateFormat.getTimeFormat(this).format(date) : "";
mMap.addMarker(new MarkerOptions()
.anchor(0.5f, 0.5f)
.position(intermediateStop)
.title(getLocationName(stop.getLocation()))
.snippet(time)
.icon(icon));
}
options.add(destination);
mMap.addPolyline(options
.width(ViewHelper.dipsToPix(getResources(), 8))
.color(legColor));
}
if (leg.getFrom().hasEntrances()) {
showEntrances(leg.getFrom().getEntrances(), false);
}
if (leg.getTo().hasEntrances()) {
showEntrances(leg.getTo().getEntrances(), true);
}
}
}
public String getRouteDescription(Leg leg) {
String description;
if (TravelMode.FOOT.equals(leg.getTravelMode())) {
description = getString(R.string.trip_map_description_walk,
DateFormat.getTimeFormat(this).format(leg.getStartTime()),
getLocationName(leg.getTo()));
} else {
description = getString(R.string.trip_map_description_normal,
DateFormat.getTimeFormat(this).format(leg.getStartTime()),
leg.getRouteName(),
leg.getHeadsing().getName());
}
return description;
}
private String getLocationName(Place location) {
// TODO: Copied from RouteDetailActivity, centralize please!
if (location == null) {
return "Unknown";
}
if (location.isMyLocation()) {
return getString(R.string.my_location);
}
return location.getName();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onLocationPermissionGranted() {
//noinspection ResourceType
mMap.setMyLocationEnabled(true);
}
@Override
public void onLocationPermissionRationale() {
Snackbar.make(findViewById(R.id.map), R.string.permission_location_needed_maps,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.allow, new View.OnClickListener() {
@Override
public void onClick(View v) {
requestLocationPermission();
}
})
.show();
}
private void setUpMap() {
if (mTransitRoute != null && mJourneyQuery != null) {
showTransitRoute(mTransitRoute);
loadIntermediateStops(mTransitRoute);
mMap.moveCamera(CameraUpdateFactory.newCameraPosition(
CameraPosition.fromLatLngZoom(mFocusedLatLng, 16)
));
} else if (mRoute != null) {
List<LatLng> latLngs = showRoute(mRoute);
zoomToFit(latLngs);
}
UiSettings settings = mMap.getUiSettings();
settings.setAllGesturesEnabled(true);
settings.setMapToolbarEnabled(false);
verifyLocationPermission();
}
private List<LatLng> showRoute(Route route) {
// If we have geometry parse and all.
List<LatLng> all = new ArrayList<>();
for (Leg leg : route.getLegs()) {
if (leg.getGeometry() != null) {
List<LatLng> latLgns = PolyUtil.decode(leg.getGeometry());
drawPolyline(latLgns, ContextCompat.getColor(this, R.color.primary));
all.addAll(latLgns);
if (leg.getSteps() != null) {
BitmapDescriptor icon = getColoredMarker(ContextCompat.getColor(
this, R.color.primary), R.drawable.ic_line_marker);
for (Step step : leg.getSteps()) {
if ("arrive".equals(step.getCode())
|| "depart".equals(step.getCode())
|| "waypoint".equals(step.getCode())) {
mMap.addMarker(new MarkerOptions()
.position(latLgns.get(step.getPosition()))
.anchor(0.5f, 0.5f)
.icon(icon));
}
}
}
}
}
return all;
}
BitmapDescriptor getMarker(@DrawableRes int drawableRes) {
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), drawableRes);
return BitmapDescriptorFactory.fromBitmap(bitmap);
}
BitmapDescriptor getColoredMarker(@ColorInt int colorInt, @DrawableRes int drawableRes) {
Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), drawableRes);
Bitmap bitmapCopy = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
Canvas canvas = new Canvas(bitmapCopy);
Paint paint = new Paint();
paint.setColorFilter(new PorterDuffColorFilter(colorInt, PorterDuff.Mode.SRC_ATOP));
canvas.drawBitmap(bitmap, 0f, 0f, paint);
return BitmapDescriptorFactory.fromBitmap(bitmapCopy);
}
private void drawPolyline(List<LatLng> latLngs, @ColorInt int color) {
Polyline poly = mMap.addPolyline(new PolylineOptions()
.zIndex(1000)
.addAll(latLngs)
.width(ViewHelper.dipsToPix(getResources(), 8))
.color(color)
.geodesic(true));
poly.setZIndex(Float.MAX_VALUE);
}
private void zoomToFit(List<LatLng> latLngs) {
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (LatLng latLng : latLngs) {
builder.include(latLng);
}
LatLngBounds bounds = builder.build();
// A "random" value for the top padding, fix to fetch from toolbar later on.
int height = ViewHelper.getDisplayHeight(this) - ViewHelper.dipsToPix(getResources(), 52);
int width = ViewHelper.getDisplayWidth(this);
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bounds, width, height,
getResources().getDimensionPixelSize(R.dimen.padding_large));
mMap.moveCamera(cu);
//mMap.animateCamera(cu);
}
/**
* Update the action bar with start and end points.
* @param journeyQuery the journey query
*/
protected void updateStartAndEndPointViews(final JourneyQuery journeyQuery) {
ActionBar ab = getSupportActionBar();
if (journeyQuery.origin.isMyLocation()) {
ab.setTitle(StringUtils.getStyledMyLocationString(this));
} else {
ab.setTitle(journeyQuery.origin.getName());
}
CharSequence via = null;
if (journeyQuery.hasVia()) {
via = journeyQuery.via.getName();
}
if (journeyQuery.destination.isMyLocation()) {
if (via != null) {
ab.setSubtitle(TextUtils.join(" • ", new CharSequence[]{via, StringUtils.getStyledMyLocationString(this)}));
} else {
ab.setSubtitle(StringUtils.getStyledMyLocationString(this));
}
} else {
if (via != null) {
ab.setSubtitle(TextUtils.join(" • ", new CharSequence[]{via, journeyQuery.destination.getName()}));
} else {
ab.setSubtitle(journeyQuery.destination.getName());
}
}
}
public void showEntrances(List<Entrance> entrances, boolean isExits) {
BitmapDescriptor icon = getMarker(R.drawable.ic_entrance_exit_12dp);
for (Entrance entrance : entrances) {
MarkerOptions markerOptions = new MarkerOptions()
.position(new LatLng(entrance.getLat(), entrance.getLon()))
.anchor(0.5f, 0.5f)
.icon(icon);
if (!TextUtils.isEmpty(entrance.getName())) {
if (isExits) {
markerOptions.title(getString(R.string.exit_and_name, entrance.getName()));
} else {
markerOptions.title(getString(R.string.entrance_and_name, entrance.getName()));
}
} else {
if (isExits) {
markerOptions.title(getString(R.string.exit));
} else {
markerOptions.title(getString(R.string.entrance));
}
}
mMap.addMarker(markerOptions);
}
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
setUpMap();
}
}